Swift のクロージャと強参照
Swift の ARC において、メモリリークを引き起こす主な原因は Retain cycle (strong reference cycle) である。ARC は、クラスのインスタンスについて、それを強参照する変数や定数の数をカウントし、0 より大きければ解放せず保持 (retain) する。この強参照による相互参照が発生した場合、それらのインスタンスをリリースすることが不可能になってしまい、結果リリース不可能なメモリ領域が生まれてしまう。これが retain cycle である。 retain cycle はクラスのインスタンス同士の相互参照でも発生し得るが、Closure の場合にも発生する。Swift では、例えば非同期処理などを行う場合には、その結果のハンドリングのために事前にコールバック関数として Closure を受け渡すのが一般的。 code:swift
func doSomething(then completion: @escaping () -> Void) {
print("do something")
completion()
}
// こう
doSomething(then: {
print("done")
})
// あるいはこう
doSomething {
print("done")
}
この時、Closure 内でローカル変数を利用したい、といった場合には、そのブロックスコープ内でスコープ外の変数が キャプチャ される。この場合、キャプチャされた変数がインスタンスを保持していると、そのインスタンスが強参照される (ARC 参照)。この性質から、Closure 内でキャプチャした参照先が、Closure 自体を保持した場合、retain cycle が発生してしまう。 例えば、適当な iOS アプリプロジェクトを立ち上げ、ViewController を以下のように定義する。
code:swift
import UIKit
class ViewController: UIViewController {
// ViewModel は何らかの方法で data を取得する
// ViewController はその取得した data をハンドリングするための DidGetData をあらかじめ登録する
typealias DidGetData = (_ data: String) -> Void
class ViewModel {
var didGetData: DidGetData?
func getDate() {
let data = "dummy data"
guard let didGetData = self.didGetData else {
return
}
didGetData(data)
}
}
// これは ViewController ではないが、あくまでダミーなので...
// ViewController が ViewModel を保持している、というイメージ
class DummyViewController {
let viewModel: ViewModel = ViewModel()
init() {
self.viewModel.didGetData = { data in
self.reloadView()
}
}
func reloadView() {}
}
override func viewDidLoad() {
super.viewDidLoad()
// インスタンスはすぐに解放する
_ = DummyViewController()
}
}
上記では、ViewModel の保持する didGetData Closure が、DummyViewController を強参照している。さらに、DummyViewController も ViewModel を強参照している。したがって、retain cycle が発生している。
https://gyazo.com/c1a990d2d474d6b56c9f6f4c07f8075c
retain cycle を引き起こさないようにするために使うのは weak や unowned によるプロパティ定義だが、コールバックの場合にも似たような記法が利用できる。[self weak] のように記述すると、強参照しなくて済む。
code:swift
class DummyViewController {
let viewModel: ViewModel = ViewModel()
init() {
self.viewModel.didGetData = { self weak data in self?.reloadView()
}
}
func reloadView() {}
}
https://gyazo.com/b0ba051d18e51168c47b8e736289365d